/**
 * \file Profiler.cpp
 * \brief Profiler definition class
 * \author Edouard ROGE
 * \date 16 October 2012
 */

// to disable warnings from windows compilation
#define  _CRT_SECURE_NO_WARNINGS

#include <algorithm>
#include <cstring>
#include "Core/Log/LogManager.h"
#include "Profiler.h"

namespace Core
{
	namespace Profile
	{
		Profiler::Profiler()
			:m_pRoot(NULL), m_OutputFilename("../data/profile.txt")
		{
		}

		Profiler::Profiler(const std::string & _OutputFilename)
			: m_pRoot(NULL), m_OutputFilename(_OutputFilename)
		{
		}

		Profiler::~Profiler()
		{
			FILE * pFile = fopen(m_OutputFilename.c_str(), "w");
			WriteDataNode(m_pRoot, 0, pFile);
			fclose(pFile);
			DeleteNodes(m_pRoot);
		}

		void Profiler::WriteDataNode(Node * _pNode, uint32 _depth, FILE * _pFile)
		{
			if (_pNode)
			{
				for (uint32 i = 0; i < _depth; ++i)
					fprintf(_pFile, "  ");

				fprintf(_pFile, "%s %d %.3f\n", _pNode->name, _pNode->nCalls, _pNode->cumulatedTime);

				Node * pCurr = _pNode->child;
				while (pCurr)
				{
					WriteDataNode(pCurr, _depth + 1, _pFile);
					pCurr = pCurr->brother;
				}
			}
		}

		void Profiler::DeleteNodes(Node * _pNode)
		{
			if (_pNode)
			{
				Node * pCurr = _pNode->child;
				while (pCurr)
				{
					Node * pBrother = pCurr->brother;
					DeleteNodes(pCurr);
					pCurr = pBrother;
				}
				delete _pNode;
			}
		}

		Profiler::Node * Profiler::GetCurrentNodeFromThreadId(std::thread::id _iThreadId) const
		{
			for (uint32 i = 0; i < m_pCurrentNodes.size(); ++i)
			{
				if (m_pCurrentNodes[i]->threadId == _iThreadId)
				{
					return m_pCurrentNodes[i];
				}
			}

			return NULL;
		}

		uint32 Profiler::StartNewTimeMeasure(const char * _strName, std::thread::id _iThreadId)
		{
			std::lock_guard<std::mutex> lock(m_mutex);
			if (!m_pRoot)
			{
				m_pRoot = new Node;
				m_pRoot->nCalls++;
				m_pRoot->threadId = _iThreadId;
				strcpy(m_pRoot->name, _strName);
				m_pCurrentNodes.push_back(m_pRoot);
			}
			// root are already created
			else
			{
				Node * pCurrent = GetCurrentNodeFromThreadId(_iThreadId);
				if (!pCurrent)
				{
					LOG_ERROR("[PROFILE] Current thread id not found, cannot profile");
					return -1;
				}
				else
				{
					// erase the node in the vector
					std::vector<Node*>::iterator it = std::find(m_pCurrentNodes.begin(), m_pCurrentNodes.end(), pCurrent);
					m_pCurrentNodes.erase(it);
				}

				Node * pNode = pCurrent->GetChild(_strName, _iThreadId);
				if (!pNode)
				{
					pNode = new Node;
					pCurrent->AddChild(pNode);
					strcpy(pNode->name, _strName);
				}

				pNode->nCalls++;
				pNode->threadId = _iThreadId;
				m_pCurrentNodes.push_back(pNode);
			}

			uint32 idTimeMeasure = m_TimeMeasure.AddMeasure();
			m_TimeMeasure.Start(idTimeMeasure);
			return idTimeMeasure;
		}

		uint32 Profiler::StartNewTimeMeasure(const char * _strName, std::thread::id _iThreadId, std::thread::id _iParentThreadId)
		{
			std::lock_guard<std::mutex> lock(m_mutex);
			if (!m_pRoot)
			{
				m_pRoot = new Node;
				m_pRoot->nCalls++;
				m_pRoot->threadId = _iThreadId;
				strcpy(m_pRoot->name, _strName);
				m_pCurrentNodes.push_back(m_pRoot);
			}
			// root are already created
			else
			{
				Node * pCurrent = GetCurrentNodeFromThreadId(_iThreadId);
				if (!pCurrent)
				{
					pCurrent = GetCurrentNodeFromThreadId(_iParentThreadId);
					if (!pCurrent)
					{
						LOG_ERROR("[PROFILE] Parent and current thread ids not found, cannot profile");
						return -1;
					}
				}
				else
				{
					// erase the node in the vector
					std::vector<Node*>::iterator it = std::find(m_pCurrentNodes.begin(), m_pCurrentNodes.end(), pCurrent);
					m_pCurrentNodes.erase(it);
				}

				Node * pNode = pCurrent->GetChild(_strName, _iThreadId);
				if (!pNode)
				{
					pNode = new Node;
					pCurrent->AddChild(pNode);
					strcpy(pNode->name, _strName);
				}

				pNode->nCalls++;
				pNode->threadId = _iThreadId;
				m_pCurrentNodes.push_back(pNode);
			}

			uint32 idTimeMeasure = m_TimeMeasure.AddMeasure();
			m_TimeMeasure.Start(idTimeMeasure);
			return idTimeMeasure;
		}

		void Profiler::StopTimeMeasure(uint32 _iIndex, std::thread::id _iThreadId)
		{
			std::lock_guard<std::mutex> lock(m_mutex);
			m_TimeMeasure.Stop(_iIndex);
			Node * pNode = GetCurrentNodeFromThreadId(_iThreadId);
			Node * pParent = pNode->parent;
			pNode->cumulatedTime += m_TimeMeasure.GetMeasure(_iIndex);

			std::vector<Node *>::iterator it = std::find(m_pCurrentNodes.begin(), m_pCurrentNodes.end(), pNode);
			if (pParent && pParent->threadId == pNode->threadId)
			{
				*it = pParent;
			}
			else
			{
				m_pCurrentNodes.erase(it);
			}
		}


		void Profiler::SetOutputFilename(const std::string & _OutputFilename)
		{
			m_OutputFilename = _OutputFilename;
		}
	}
}